Completed
Pull Request — master (#100)
by thomas
01:10
created

api_client.js ➔ APIClient   B

Complexity

Conditions 3
Paths 9

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 3
c 3
b 1
f 0
nc 9
dl 0
loc 27
rs 8.8571
nop 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    RestClient = require('./rest_client'),
11
    Encryption = require('./encryption'),
12
    KeyDerivation = require('./keyderivation'),
13
    EncryptionMnemonic = require('./encryption_mnemonic'),
14
    blocktrail = require('./blocktrail'),
15
    randomBytes = require('randombytes'),
16
    CryptoJS = require('crypto-js'),
17
    webworkifier = require('./webworkifier');
18
19
/**
20
 *
21
 * @param opt
22
 * @returns {*}
23
 */
24
function networkFromOptions(opt) {
25
    if (opt.bitcoinCash) {
26
        if (opt.regtest) {
27
            return bitcoin.networks.bitcoincashregtest;
28
        } else if (opt.testnet) {
29
            return bitcoin.networks.bitcoincashtestnet;
30
        } else {
31
            return bitcoin.networks.bitcoincash;
32
        }
33
    } else {
34
        if (opt.regtest) {
35
            return bitcoin.networks.regtest;
36
        } else if (opt.testnet) {
37
            return bitcoin.networks.testnet;
38
        } else {
39
            return bitcoin.networks.bitcoin;
40
        }
41
    }
42
}
43
44
var useWebWorker = require('./use-webworker')();
45
46
/**
47
 * Bindings to conssume the BlockTrail API
48
 *
49
 * @param options       object{
50
 *                          apiKey: 'API_KEY',
51
 *                          apiSecret: 'API_SECRET',
52
 *                          host: 'defaults to api.blocktrail.com',
53
 *                          network: 'BTC|LTC',
54
 *                          testnet: true|false
55
 *                      }
56
 * @constructor
57
 */
58
var APIClient = function(options) {
59
    var self = this;
60
61
    // handle constructor call without 'new'
62
    if (!(this instanceof APIClient)) {
63
        return new APIClient(options);
64
    }
65
66
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
67
    options.network = normalizedNetwork[0];
68
    options.testnet = normalizedNetwork[1];
69
    options.regtest = normalizedNetwork[2];
70
    // apiNetwork we allow to be customized for debugging purposes
71
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
72
73
    self.bitcoinCash = options.network === "BCC";
74
    self.regtest = options.regtest;
75
    self.testnet = options.testnet;
76
    self.network = networkFromOptions(self);
77
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
78
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
79
80
    /**
81
     * @type RestClient
82
     */
83
    self.client = APIClient.initRestClient(options);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
84
};
85
86
APIClient.normalizeNetworkFromOptions = function(options) {
87
    /* jshint -W071, -W074 */
88
    var network = 'BTC';
89
    var testnet = false;
90
    var regtest = false;
91
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
92
93
    var prefix;
94
    var done = false;
95
96
    if (options.network) {
97
        var lower = options.network.toLowerCase();
98
99
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
100
        if (!m) {
101
            throw new Error("Invalid network [" + options.network + "]");
102
        }
103
104
        if (m[2] === 'btc') {
105
            network = "BTC";
106
        } else {
107
            network = "BCC";
108
        }
109
110
        prefix = m[1];
111
        if (prefix) {
112
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
113
            done = true;
114
            if (prefix === 'r') {
115
                testnet = true;
116
                regtest = true;
117
            } else if (prefix === 't') {
118
                testnet = true;
119
            }
120
        }
121
    }
122
123
    // if we're not already done then apply options.regtest and options.testnet
124
    if (!done) {
125
        if (options.regtest) {
126
            testnet = true;
127
            regtest = true;
128
            prefix = "r";
129
        } else if (options.testnet) {
130
            testnet = true;
131
            prefix = "t";
132
        }
133
    }
134
135
    apiNetwork = (prefix || "") + network;
136
137
    return [network, testnet, regtest, apiNetwork];
138
};
139
140
APIClient.initRestClient = function(options) {
141
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
142
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
143
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
144
    }
145
146
    // trim off leading https?://
147
    if (options.host && options.host.indexOf("https://") === 0) {
148
        options.https = true;
149
        options.host = options.host.substr(8);
150
    } else if (options.host && options.host.indexOf("http://") === 0) {
151
        options.https = false;
152
        options.host = options.host.substr(7);
153
    }
154
155
    if (typeof options.https === "undefined") {
156
        options.https = true;
157
    }
158
159
    if (!options.host) {
160
        options.host = 'api.blocktrail.com';
161
    }
162
163
    if (!options.port) {
164
        options.port = options.https ? 443 : 80;
165
    }
166
167
    if (!options.endpoint) {
168
        options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
169
    }
170
171
    return new RestClient(options);
172
};
173
174
var determineDataStorageV2_3 = function(options) {
175
    return q.when(options)
176
        .then(function(options) {
177
            // legacy
178
            if (options.storePrimaryMnemonic) {
179
                options.storeDataOnServer = options.storePrimaryMnemonic;
180
            }
181
182
            // storeDataOnServer=false when primarySeed is provided
183
            if (typeof options.storeDataOnServer === "undefined") {
184
                options.storeDataOnServer = !options.primarySeed;
185
            }
186
187
            return options;
188
        });
189
};
190
191
var produceEncryptedDataV2 = function(options, notify) {
192
    return q.when(options)
193
        .then(function(options) {
194
            if (options.storeDataOnServer) {
195
                if (!options.secret) {
196
                    if (!options.passphrase) {
197
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
198
                    }
199
200
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
201
202
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
203
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
204
                }
205
206
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
207
208
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
209
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
210
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
211
212
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
213
214
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
215
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
216
            }
217
218
            return options;
219
        });
220
};
221
222
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
223
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
224
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
225
        var saltBuf = Encryption.generateSalt();
226
        var iv = Encryption.generateIV();
227
228
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
229
            return require('./webworker');
230
        }, onLoadWorkerLoadAsmCrypto, {
231
            method: 'Encryption.encryptWithSaltAndIV',
232
            pt: pt,
233
            pw: pw,
234
            saltBuf: saltBuf,
235
            iv: iv,
236
            iterations: iter
237
        })
238
            .then(function(data) {
239
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
240
            });
241
    } else {
242
        try {
243
            return q.when(Encryption.encrypt(pt, pw, iter));
244
        } catch (e) {
245
            return q.reject(e);
246
        }
247
    }
248
};
249
250
APIClient.prototype.promisedDecrypt = function(ct, pw) {
251
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
252
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
253
            return require('./webworker');
254
        }, onLoadWorkerLoadAsmCrypto, {
255
            method: 'Encryption.decrypt',
256
            ct: ct,
257
            pw: pw
258
        })
259
            .then(function(data) {
260
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
261
            });
262
    } else {
263
        try {
264
            return q.when(Encryption.decrypt(ct, pw));
265
        } catch (e) {
266
            return q.reject(e);
267
        }
268
    }
269
};
270
271
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
272
    var self = this;
273
274
    return q.when(options)
275
        .then(function(options) {
276
            if (options.storeDataOnServer) {
277
                return q.when()
278
                    .then(function() {
279
                        if (!options.secret) {
280
                            if (!options.passphrase) {
281
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
282
                            }
283
284
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
285
286
                            // -> now a buffer
287
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
288
289
                            // -> now a buffer
290
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
291
                                .then(function(encryptedSecret) {
292
                                    options.encryptedSecret = encryptedSecret;
293
                                });
294
                        } else {
295
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
296
                                throw new Error('Secret must be a buffer');
297
                            }
298
                        }
299
                    })
300
                    .then(function() {
301
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
302
303
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
304
                            .then(function(encryptedPrimarySeed) {
305
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
306
                            });
307
                    })
308
                    .then(function() {
309
                        // skip generating recovery secret when explicitly set to false
310
                        if (options.recoverySecret === false) {
311
                            return;
312
                        }
313
314
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
315
                        if (!options.recoverySecret) {
316
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
317
                        }
318
319
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
320
                            .then(function(recoveryEncryptedSecret) {
321
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
322
                            });
323
                    })
324
                    .then(function() {
325
                        return options;
326
                    });
327
            } else {
328
                return options;
329
            }
330
        });
331
};
332
333
var doRemainingWalletDataV2_3 = function(options, network, notify) {
334
    return q.when(options)
335
        .then(function(options) {
336
            if (!options.backupPublicKey) {
337
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
338
            }
339
340
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
341
342
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
343
344
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
345
346
            if (!options.backupPublicKey) {
347
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
348
                options.backupPublicKey = options.backupPrivateKey.neutered();
349
            }
350
351
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
352
353
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
354
355
            return options;
356
        });
357
};
358
359
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
360
    var self = this;
361
362
    var deferred = q.defer();
363
    deferred.promise.spreadNodeify(cb);
364
365
    deferred.resolve(q.fcall(function() {
366
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
367
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
368
        });
369
    }));
370
371
    return deferred.promise;
372
};
373
374
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
375
    var self = this;
376
377
    if (useWebWorker) {
378
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
379
            return require('./webworker');
380
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
381
            .then(function(data) {
382
                return data.seed;
383
            });
384
    } else {
385
        try {
386
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
387
        } catch (e) {
388
            return q.reject(e);
389
        }
390
    }
391
};
392
393
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
394
    var self = this;
395
396
    var deferred = q.defer();
397
    deferred.promise.nodeify(cb);
398
399
    try {
400
        // avoid conflicting options
401
        if (options.passphrase && options.password) {
402
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
403
        }
404
        // normalize passphrase/password
405
        options.passphrase = options.passphrase || options.password;
406
        delete options.password;
407
408
        // avoid conflicting options
409
        if (options.primaryMnemonic && options.primarySeed) {
410
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
411
        }
412
413
        // avoid deprecated options
414
        if (options.primaryPrivateKey) {
415
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
416
        }
417
418
        // make sure we have at least one thing to use
419
        if (!options.primaryMnemonic && !options.primarySeed) {
420
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
421
        }
422
423
        if (options.primarySeed) {
424
            self.primarySeed = options.primarySeed;
425
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
426
            deferred.resolve(options);
427
        } else {
428
            if (!options.passphrase) {
429
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
430
            }
431
432
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
433
                .then(function(seedHex) {
434
                    try {
435
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
436
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
437
                        deferred.resolve(options);
438
                    } catch (e) {
439
                        deferred.reject(e);
440
                    }
441
                }, function(e) {
442
                    deferred.reject(e);
443
                });
444
        }
445
    } catch (e) {
446
        deferred.reject(e);
447
    }
448
449
    return deferred.promise;
450
};
451
452
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
453
    var self = this;
454
455
    var deferred = q.defer();
456
    deferred.promise.nodeify(cb);
457
458
    try {
459
        // avoid conflicting options
460
        if (options.backupMnemonic && options.backupPublicKey) {
461
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
462
        }
463
464
        // make sure we have at least one thing to use
465
        if (!options.backupMnemonic && !options.backupPublicKey) {
466
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
467
        }
468
469
        if (options.backupPublicKey) {
470
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
471
                deferred.resolve(options);
472
            } else {
473
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
474
                deferred.resolve(options);
475
            }
476
        } else {
477
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
478
                options.backupPublicKey = backupPrivateKey.neutered();
479
                deferred.resolve(options);
480
            }, function(e) {
481
                deferred.reject(e);
482
            });
483
        }
484
    } catch (e) {
485
        deferred.reject(e);
486
    }
487
488
    return deferred.promise;
489
};
490
491
APIClient.prototype.debugAuth = function(cb) {
492
    var self = this;
493
494
    return self.client.get("/debug/http-signature", null, true, cb);
495
};
496
497
/**
498
 * get a single address
499
 *
500
 * @param address      string       address hash
501
 * @param [cb]          function    callback function to call when request is complete
502
 * @return q.Promise
503
 */
504
APIClient.prototype.address = function(address, cb) {
505
    var self = this;
506
507
    return self.client.get("/address/" + address, null, cb);
508
};
509
510
APIClient.prototype.addresses = function(addresses, cb) {
511
    var self = this;
512
513
    return self.client.post("/address", null, {"addresses": addresses}, cb);
514
};
515
516
/**
517
 * get all transactions for an address (paginated)
518
 *
519
 * @param address       string      address hash
520
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
521
 * @param [cb]          function    callback function to call when request is complete
522
 * @return q.Promise
523
 */
524
APIClient.prototype.addressTransactions = function(address, params, cb) {
525
    var self = this;
526
527
    if (typeof params === "function") {
528
        cb = params;
529
        params = null;
530
    }
531
532
    return self.client.get("/address/" + address + "/transactions", params, cb);
533
};
534
535
/**
536
 * get all transactions for a batch of addresses (paginated)
537
 *
538
 * @param addresses     array       address hashes
539
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
540
 * @param [cb]          function    callback function to call when request is complete
541
 * @return q.Promise
542
 */
543
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
544
    var self = this;
545
546
    if (typeof params === "function") {
547
        cb = params;
548
        params = null;
549
    }
550
551
    return self.client.post("/address/has-transactions", params, {"addresses": addresses}, cb);
552
};
553
554
/**
555
 * get all unconfirmed transactions for an address (paginated)
556
 *
557
 * @param address       string      address hash
558
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
559
 * @param [cb]          function    callback function to call when request is complete
560
 * @return q.Promise
561
 */
562
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
563
    var self = this;
564
565
    if (typeof params === "function") {
566
        cb = params;
567
        params = null;
568
    }
569
570
    return self.client.get("/address/" + address + "/unconfirmed-transactions", params, cb);
571
};
572
573
/**
574
 * get all unspent outputs for an address (paginated)
575
 *
576
 * @param address       string      address hash
577
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
578
 * @param [cb]          function    callback function to call when request is complete
579
 * @return q.Promise
580
 */
581
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
582
    var self = this;
583
584
    if (typeof params === "function") {
585
        cb = params;
586
        params = null;
587
    }
588
589
    return self.client.get("/address/" + address + "/unspent-outputs", params, cb);
590
};
591
592
/**
593
 * get all unspent outputs for a batch of addresses (paginated)
594
 *
595
 * @param addresses     array       address hashes
596
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
597
 * @param [cb]          function    callback function to call when request is complete
598
 * @return q.Promise
599
 */
600
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
601
    var self = this;
602
603
    if (typeof params === "function") {
604
        cb = params;
605
        params = null;
606
    }
607
608
    return self.client.post("/address/unspent-outputs", params, {"addresses": addresses}, cb);
609
};
610
611
/**
612
 * verify ownership of an address
613
 *
614
 * @param address       string      address hash
615
 * @param signature     string      a signed message (the address hash) using the private key of the address
616
 * @param [cb]          function    callback function to call when request is complete
617
 * @return q.Promise
618
 */
619
APIClient.prototype.verifyAddress = function(address, signature, cb) {
620
    var self = this;
621
622
    return self.client.post("/address/" + address + "/verify", null, {signature: signature}, cb);
623
};
624
625
/**
626
 * get all blocks (paginated)
627
 *
628
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
629
 * @param [cb]          function    callback function to call when request is complete
630
 * @return q.Promise
631
 */
632
APIClient.prototype.allBlocks = function(params, cb) {
633
    var self = this;
634
635
    if (typeof params === "function") {
636
        cb = params;
637
        params = null;
638
    }
639
640
    return self.client.get("/all-blocks", params, cb);
641
};
642
643
/**
644
 * get a block
645
 *
646
 * @param block         string|int  a block hash or a block height
647
 * @param [cb]          function    callback function to call when request is complete
648
 * @return q.Promise
649
 */
650
APIClient.prototype.block = function(block, cb) {
651
    var self = this;
652
653
    return self.client.get("/block/" + block, null, cb);
654
};
655
656
/**
657
 * get the latest block
658
 *
659
 * @param [cb]          function    callback function to call when request is complete
660
 * @return q.Promise
661
 */
662
APIClient.prototype.blockLatest = function(cb) {
663
    var self = this;
664
665
    return self.client.get("/block/latest", null, cb);
666
};
667
668
/**
669
 * get all transactions for a block (paginated)
670
 *
671
 * @param block         string|int  a block hash or a block height
672
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
673
 * @param [cb]          function    callback function to call when request is complete
674
 * @return q.Promise
675
 */
676
APIClient.prototype.blockTransactions = function(block, params, cb) {
677
    var self = this;
678
679
    if (typeof params === "function") {
680
        cb = params;
681
        params = null;
682
    }
683
684
    return self.client.get("/block/" + block + "/transactions", params, cb);
685
};
686
687
/**
688
 * get a single transaction
689
 *
690
 * @param tx            string      transaction hash
691
 * @param [cb]          function    callback function to call when request is complete
692
 * @return q.Promise
693
 */
694
APIClient.prototype.transaction = function(tx, cb) {
695
    var self = this;
696
697
    return self.client.get("/transaction/" + tx, null, cb);
698
};
699
700
/**
701
 * get a batch of transactions
702
 *
703
 * @param txs           string[]    list of transaction hashes (txId)
704
 * @param [cb]          function    callback function to call when request is complete
705
 * @return q.Promise
706
 */
707
APIClient.prototype.transactions = function(txs, cb) {
708
    var self = this;
709
710
    return self.client.post("/transactions", null, txs, cb, false);
711
};
712
713
/**
714
 * get a paginated list of all webhooks associated with the api user
715
 *
716
 * @param [params]      object      pagination: {page: 1, limit: 20}
717
 * @param [cb]          function    callback function to call when request is complete
718
 * @return q.Promise
719
 */
720
APIClient.prototype.allWebhooks = function(params, cb) {
721
    var self = this;
722
723
    if (typeof params === "function") {
724
        cb = params;
725
        params = null;
726
    }
727
728
    return self.client.get("/webhooks", params, cb);
729
};
730
731
/**
732
 * create a new webhook
733
 *
734
 * @param url           string      the url to receive the webhook events
735
 * @param [identifier]  string      a unique identifier associated with the webhook
736
 * @param [cb]          function    callback function to call when request is complete
737
 * @return q.Promise
738
 */
739
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
740
    var self = this;
741
742
    if (typeof identifier === "function") {
743
        //mimic function overloading
744
        cb = identifier;
745
        identifier = null;
746
    }
747
748
    return self.client.post("/webhook", null, {url: url, identifier: identifier}, cb);
749
};
750
751
/**
752
 * Converts a cash address to the legacy (base58) format
753
 * @param {string} input
754
 * @returns {string}
755
 */
756
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
757
    if (this.network === bitcoin.networks.bitcoincash ||
758
        this.network === bitcoin.networks.bitcoincashtestnet ||
759
        this.network === bitcoin.networks.bitcoincashregtest) {
760
        var address;
761
        try {
762
            bitcoin.address.fromBase58Check(input, this.network);
763
            return input;
764
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
765
766
        address = bitcoin.address.fromCashAddress(input, this.network);
767
        var prefix;
768
        if (address.version === bitcoin.script.types.P2PKH) {
769
            prefix = this.network.pubKeyHash;
770
        } else if (address.version === bitcoin.script.types.P2SH) {
771
            prefix = this.network.scriptHash;
772
        } else {
773
            throw new Error("Unsupported address type");
774
        }
775
776
        return bitcoin.address.toBase58Check(address.hash, prefix);
777
    }
778
779
    throw new Error("Cash addresses only work on bitcoin cash");
780
};
781
782
/**
783
 * Converts a legacy bitcoin to the new cashaddr format
784
 * @param {string} input
785
 * @returns {string}
786
 */
787
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
788
    if (this.network === bitcoin.networks.bitcoincash ||
789
        this.network === bitcoin.networks.bitcoincashtestnet ||
790
        this.network === bitcoin.networks.bitcoincashregtest
791
    ) {
792
        var address;
793
        try {
794
            bitcoin.address.fromCashAddress(input, this.network);
795
            return input;
796
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
797
798
        address = bitcoin.address.fromBase58Check(input, this.network);
799
        var scriptType;
800
        if (address.version === this.network.pubKeyHash) {
801
            scriptType = bitcoin.script.types.P2PKH;
802
        } else if (address.version === this.network.scriptHash) {
803
            scriptType = bitcoin.script.types.P2SH;
804
        } else {
805
            throw new Error("Unsupported address type");
806
        }
807
808
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
809
    }
810
811
    throw new Error("Cash addresses only work on bitcoin cash");
812
};
813
814
/**
815
 * get an existing webhook by it's identifier
816
 *
817
 * @param identifier    string      the unique identifier of the webhook to get
818
 * @param [cb]          function    callback function to call when request is complete
819
 * @return q.Promise
820
 */
821
APIClient.prototype.getWebhook = function(identifier, cb) {
822
    var self = this;
823
824
    return self.client.get("/webhook/" + identifier, null, cb);
825
};
826
827
/**
828
 * update an existing webhook
829
 *
830
 * @param identifier    string      the unique identifier of the webhook
831
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
832
 * @param [cb]          function    callback function to call when request is complete
833
 * @return q.Promise
834
 */
835
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
836
    var self = this;
837
838
    return self.client.put("/webhook/" + identifier, null, webhookData, cb);
839
};
840
841
/**
842
 * deletes an existing webhook and any event subscriptions associated with it
843
 *
844
 * @param identifier    string      the unique identifier of the webhook
845
 * @param [cb]          function    callback function to call when request is complete
846
 * @return q.Promise
847
 */
848
APIClient.prototype.deleteWebhook = function(identifier, cb) {
849
    var self = this;
850
851
    return self.client.delete("/webhook/" + identifier, null, null, cb);
852
};
853
854
/**
855
 * get a paginated list of all the events a webhook is subscribed to
856
 *
857
 * @param identifier    string      the unique identifier of the webhook
858
 * @param [params]      object      pagination: {page: 1, limit: 20}
859
 * @param [cb]          function    callback function to call when request is complete
860
 * @return q.Promise
861
 */
862
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
863
    var self = this;
864
865
    if (typeof params === "function") {
866
        cb = params;
867
        params = null;
868
    }
869
870
    return self.client.get("/webhook/" + identifier + "/events", params, cb);
871
};
872
873
/**
874
 * subscribes a webhook to transaction events for a particular transaction
875
 *
876
 * @param identifier    string      the unique identifier of the webhook
877
 * @param transaction   string      the transaction hash
878
 * @param confirmations integer     the amount of confirmations to send
879
 * @param [cb]          function    callback function to call when request is complete
880
 * @return q.Promise
881
 */
882
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
883
    var self = this;
884
    var postData = {
885
        'event_type': 'transaction',
886
        'transaction': transaction,
887
        'confirmations': confirmations
888
    };
889
890
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
891
};
892
893
/**
894
 * subscribes a webhook to transaction events on a particular address
895
 *
896
 * @param identifier    string      the unique identifier of the webhook
897
 * @param address       string      the address hash
898
 * @param confirmations integer     the amount of confirmations to send
899
 * @param [cb]          function    callback function to call when request is complete
900
 * @return q.Promise
901
 */
902
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
903
    var self = this;
904
    var postData = {
905
        'event_type': 'address-transactions',
906
        'address': address,
907
        'confirmations': confirmations
908
    };
909
910
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
911
};
912
913
/**
914
 * batch subscribes a webhook to multiple transaction events
915
 *
916
 * @param  identifier   string      the unique identifier of the webhook
917
 * @param  batchData    array       An array of objects containing batch event data:
918
 *                                  {address : 'address', confirmations : 'confirmations']
919
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
920
 * @param [cb]          function    callback function to call when request is complete
921
 * @return q.Promise
922
 */
923
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
924
    var self = this;
925
    batchData.forEach(function(record) {
926
        record.event_type = 'address-transactions';
927
    });
928
929
    return self.client.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
930
};
931
932
/**
933
 * subscribes a webhook to a new block event
934
 *
935
 * @param identifier    string      the unique identifier of the webhook
936
 * @param [cb]          function    callback function to call when request is complete
937
 * @return q.Promise
938
 */
939
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
940
    var self = this;
941
    var postData = {
942
        'event_type': 'block'
943
    };
944
945
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
946
};
947
948
/**
949
 * removes an address transaction event subscription from a webhook
950
 *
951
 * @param identifier    string      the unique identifier of the webhook
952
 * @param address       string      the address hash
953
 * @param [cb]          function    callback function to call when request is complete
954
 * @return q.Promise
955
 */
956
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
957
    var self = this;
958
959
    return self.client.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
960
};
961
962
/**
963
 * removes an transaction event subscription from a webhook
964
 *
965
 * @param identifier    string      the unique identifier of the webhook
966
 * @param transaction   string      the transaction hash
967
 * @param [cb]          function    callback function to call when request is complete
968
 * @return q.Promise
969
 */
970
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
971
    var self = this;
972
973
    return self.client.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
974
};
975
976
/**
977
 * removes a block event subscription from a webhook
978
 *
979
 * @param identifier    string      the unique identifier of the webhook
980
 * @param [cb]          function    callback function to call when request is complete
981
 * @return q.Promise
982
 */
983
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
984
    var self = this;
985
986
    return self.client.delete("/webhook/" + identifier + "/block", null, null, cb);
987
};
988
989
/**
990
 * initialize an existing wallet
991
 *
992
 * Either takes two argument:
993
 * @param options       object      {}
994
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
995
 *
996
 * Or takes three arguments (old, deprecated syntax):
997
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
998
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
999
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 994. The second definition is ignored.
Loading history...
1000
 *
1001
 * @returns {q.Promise}
1002
 */
1003
APIClient.prototype.initWallet = function(options, cb) {
1004
    var self = this;
1005
1006
    if (typeof options !== "object") {
1007
        // get the old-style arguments
1008
        options = {
1009
            identifier: arguments[0],
1010
            passphrase: arguments[1]
1011
        };
1012
1013
        cb = arguments[2];
1014
    }
1015
1016
    if (options.check_backup_key) {
1017
        if (typeof options.check_backup_key !== "string") {
1018
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1019
        }
1020
    }
1021
1022
    var deferred = q.defer();
1023
    deferred.promise.spreadNodeify(cb);
1024
1025
    var identifier = options.identifier;
1026
1027
    if (!identifier) {
1028
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1029
        return deferred.promise;
1030
    }
1031
1032
    deferred.resolve(self.client.get("/wallet/" + identifier, null, true).then(function(result) {
1033
        var keyIndex = options.keyIndex || result.key_index;
1034
1035
        options.walletVersion = result.wallet_version;
1036
1037
        if (options.check_backup_key) {
1038
            if (options.check_backup_key !== result.backup_public_key[0]) {
1039
                throw new Error("Backup key returned from server didn't match our own copy");
1040
            }
1041
        }
1042
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1043
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1044
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1045
        });
1046
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1047
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1048
        });
1049
1050
        // initialize wallet
1051
        var wallet = new Wallet(
1052
            self,
1053
            identifier,
1054
            options.walletVersion,
1055
            result.primary_mnemonic,
1056
            result.encrypted_primary_seed,
1057
            result.encrypted_secret,
1058
            primaryPublicKeys,
1059
            backupPublicKey,
1060
            blocktrailPublicKeys,
1061
            keyIndex,
1062
            result.segwit || 0,
1063
            self.testnet,
1064
            self.regtest,
1065
            result.checksum,
1066
            result.upgrade_key_index,
1067
            options.useCashAddress,
1068
            options.bypassNewAddressCheck
1069
        );
1070
1071
        wallet.recoverySecret = result.recovery_secret;
1072
1073
        if (!options.readOnly) {
1074
            return wallet.unlock(options).then(function() {
1075
                return wallet;
1076
            });
1077
        } else {
1078
            return wallet;
1079
        }
1080
    }));
1081
1082
    return deferred.promise;
1083
};
1084
1085
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1086
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1087
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1088
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1089
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1090
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1091
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1092
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1093
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1094
1095
/**
1096
 * create a new wallet
1097
 *   - will generate a new primary seed and backup seed
1098
 *
1099
 * Either takes two argument:
1100
 * @param options       object      {}
1101
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) // nocommit @TODO
1102
 *
1103
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1104
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1100. The second definition is ignored.
Loading history...
1105
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1101. The second definition is ignored.
Loading history...
1106
 *
1107
 * Or takes four arguments (old, deprecated syntax):
1108
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1109
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1110
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1111
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1101. The second definition is ignored.
Loading history...
1112
 * @returns {q.Promise}
1113
 */
1114
APIClient.prototype.createNewWallet = function(options, cb) {
1115
    /* jshint -W071, -W074 */
1116
1117
    var self = this;
1118
1119
    if (typeof options !== "object") {
1120
        // get the old-style arguments
1121
        var identifier = arguments[0];
1122
        var passphrase = arguments[1];
1123
        var keyIndex = arguments[2];
1124
        cb = arguments[3];
1125
1126
        // keyIndex is optional
1127
        if (typeof keyIndex === "function") {
1128
            cb = keyIndex;
1129
            keyIndex = null;
1130
        }
1131
1132
        options = {
1133
            identifier: identifier,
1134
            passphrase: passphrase,
1135
            keyIndex: keyIndex
1136
        };
1137
    }
1138
1139
    // default to v3
1140
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1141
1142
    var deferred = q.defer();
1143
    deferred.promise.spreadNodeify(cb);
1144
1145
    q.nextTick(function() {
1146
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1147
1148
        options.keyIndex = options.keyIndex || 0;
1149
        options.passphrase = options.passphrase || options.password;
1150
        delete options.password;
1151
1152
        if (!options.identifier) {
1153
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1154
            return deferred.promise;
1155
        }
1156
1157
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1158
            self._createNewWalletV1(options)
1159
                .progress(function(p) { deferred.notify(p); })
1160
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1161
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1162
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1163
            self._createNewWalletV2(options)
1164
                .progress(function(p) { deferred.notify(p); })
1165
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1166
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1167
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1168
            self._createNewWalletV3(options)
1169
                .progress(function(p) { deferred.notify(p); })
1170
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1171
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1172
        } else {
1173
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1174
        }
1175
    });
1176
1177
    return deferred.promise;
1178
};
1179
1180
APIClient.prototype._createNewWalletV1 = function(options) {
1181
    var self = this;
1182
1183
    var deferred = q.defer();
1184
1185
    q.nextTick(function() {
1186
1187
        if (!options.primaryMnemonic && !options.primarySeed) {
1188
            if (!options.passphrase && !options.password) {
1189
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1190
                return deferred.promise;
1191
            } else {
1192
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1193
                if (options.storePrimaryMnemonic !== false) {
1194
                    options.storePrimaryMnemonic = true;
1195
                }
1196
            }
1197
        }
1198
1199
        if (!options.backupMnemonic && !options.backupPublicKey) {
1200
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1201
        }
1202
1203
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1204
1205
        self.resolvePrimaryPrivateKeyFromOptions(options)
1206
            .then(function(options) {
1207
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1208
1209
                return self.resolveBackupPublicKeyFromOptions(options)
1210
                    .then(function(options) {
1211
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1212
1213
                        // create a checksum of our private key which we'll later use to verify we used the right password
1214
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1215
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1216
                        var keyIndex = options.keyIndex;
1217
1218
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1219
1220
                        // send the public keys to the server to store them
1221
                        //  and the mnemonic, which is safe because it's useless without the password
1222
                        return self.storeNewWalletV1(
1223
                            options.identifier,
1224
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1225
                            [options.backupPublicKey.toBase58(), "M"],
1226
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1227
                            checksum,
1228
                            keyIndex,
1229
                            options.segwit || null
1230
                        )
1231
                            .then(function(result) {
1232
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1233
1234
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1235
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1236
                                });
1237
1238
                                var wallet = new Wallet(
1239
                                    self,
1240
                                    options.identifier,
1241
                                    Wallet.WALLET_VERSION_V1,
1242
                                    options.primaryMnemonic,
1243
                                    null,
1244
                                    null,
1245
                                    {keyIndex: primaryPublicKey},
1246
                                    options.backupPublicKey,
1247
                                    blocktrailPublicKeys,
1248
                                    keyIndex,
1249
                                    result.segwit || 0,
1250
                                    self.testnet,
1251
                                    self.regtest,
1252
                                    checksum,
1253
                                    result.upgrade_key_index,
1254
                                    options.useCashAddress,
1255
                                    options.bypassNewAddressCheck
1256
                                );
1257
1258
                                return wallet.unlock({
1259
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1260
                                    passphrase: options.passphrase,
1261
                                    primarySeed: options.primarySeed,
1262
                                    primaryMnemonic: null // explicit null
1263
                                }).then(function() {
1264
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1265
                                    return [
1266
                                        wallet,
1267
                                        {
1268
                                            walletVersion: wallet.walletVersion,
1269
                                            primaryMnemonic: options.primaryMnemonic,
1270
                                            backupMnemonic: options.backupMnemonic,
1271
                                            blocktrailPublicKeys: blocktrailPublicKeys
1272
                                        }
1273
                                    ];
1274
                                });
1275
                            });
1276
                    }
1277
                );
1278
            })
1279
            .then(
1280
            function(r) {
1281
                deferred.resolve(r);
1282
            },
1283
            function(e) {
1284
                deferred.reject(e);
1285
            }
1286
        )
1287
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1288
    });
1289
1290
    return deferred.promise;
1291
};
1292
1293 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1294
    var self = this;
1295
1296
    var deferred = q.defer();
1297
1298
    // avoid modifying passed options
1299
    options = _.merge({}, options);
1300
1301
    determineDataStorageV2_3(options)
1302
        .then(function(options) {
1303
            options.passphrase = options.passphrase || options.password;
1304
            delete options.password;
1305
1306
            // avoid deprecated options
1307
            if (options.primaryPrivateKey) {
1308
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1309
            }
1310
1311
            // seed should be provided or generated
1312
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1313
1314
            return options;
1315
        })
1316
        .then(function(options) {
1317
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1318
        })
1319
        .then(function(options) {
1320
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1321
        })
1322
        .then(function(options) {
1323
            // create a checksum of our private key which we'll later use to verify we used the right password
1324
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1325
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1326
            var keyIndex = options.keyIndex;
1327
1328
            // send the public keys and encrypted data to server
1329
            return self.storeNewWalletV2(
1330
                options.identifier,
1331
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1332
                [options.backupPublicKey.toBase58(), "M"],
1333
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1334
                options.storeDataOnServer ? options.encryptedSecret : false,
1335
                options.storeDataOnServer ? options.recoverySecret : false,
1336
                checksum,
1337
                keyIndex,
1338
                options.support_secret || null,
1339
                options.segwit || null
1340
            )
1341
                .then(
1342
                function(result) {
1343
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1344
1345
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1346
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1347
                    });
1348
1349
                    var wallet = new Wallet(
1350
                        self,
1351
                        options.identifier,
1352
                        Wallet.WALLET_VERSION_V2,
1353
                        null,
1354
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1355
                        options.storeDataOnServer ? options.encryptedSecret : null,
1356
                        {keyIndex: options.primaryPublicKey},
1357
                        options.backupPublicKey,
1358
                        blocktrailPublicKeys,
1359
                        keyIndex,
1360
                        result.segwit || 0,
1361
                        self.testnet,
1362
                        self.regtest,
1363
                        checksum,
1364
                        result.upgrade_key_index,
1365
                        options.useCashAddress,
1366
                        options.bypassNewAddressCheck
1367
                    );
1368
1369
                    // pass along decrypted data to avoid extra work
1370
                    return wallet.unlock({
1371
                        walletVersion: Wallet.WALLET_VERSION_V2,
1372
                        passphrase: options.passphrase,
1373
                        primarySeed: options.primarySeed,
1374
                        secret: options.secret
1375
                    }).then(function() {
1376
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1377
                        return [
1378
                            wallet,
1379
                            {
1380
                                walletVersion: wallet.walletVersion,
1381
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1382
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1383
                                    null,
1384
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1385
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1386
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1387
                                    null,
1388
                                encryptedSecret: options.encryptedSecret ?
1389
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1390
                                    null,
1391
                                blocktrailPublicKeys: blocktrailPublicKeys
1392
                            }
1393
                        ];
1394
                    });
1395
                }
1396
            );
1397
        })
1398
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1399
1400
    return deferred.promise;
1401
};
1402
1403 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1404
    var self = this;
1405
1406
    var deferred = q.defer();
1407
1408
    // avoid modifying passed options
1409
    options = _.merge({}, options);
1410
1411
    determineDataStorageV2_3(options)
1412
        .then(function(options) {
1413
            options.passphrase = options.passphrase || options.password;
1414
            delete options.password;
1415
1416
            // avoid deprecated options
1417
            if (options.primaryPrivateKey) {
1418
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1419
            }
1420
1421
            // seed should be provided or generated
1422
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1423
1424
            return options;
1425
        })
1426
        .then(function(options) {
1427
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1428
        })
1429
        .then(function(options) {
1430
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1431
        })
1432
        .then(function(options) {
1433
            // create a checksum of our private key which we'll later use to verify we used the right password
1434
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1435
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1436
            var keyIndex = options.keyIndex;
1437
1438
            // send the public keys and encrypted data to server
1439
            return self.storeNewWalletV3(
1440
                options.identifier,
1441
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1442
                [options.backupPublicKey.toBase58(), "M"],
1443
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1444
                options.storeDataOnServer ? options.encryptedSecret : false,
1445
                options.storeDataOnServer ? options.recoverySecret : false,
1446
                checksum,
1447
                keyIndex,
1448
                options.support_secret || null,
1449
                options.segwit || null
1450
            )
1451
                .then(
1452
                    // result, deferred, self(apiclient)
1453
                    function(result) {
1454
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1455
1456
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1457
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1458
                        });
1459
1460
                        var wallet = new Wallet(
1461
                            self,
1462
                            options.identifier,
1463
                            Wallet.WALLET_VERSION_V3,
1464
                            null,
1465
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1466
                            options.storeDataOnServer ? options.encryptedSecret : null,
1467
                            {keyIndex: options.primaryPublicKey},
1468
                            options.backupPublicKey,
1469
                            blocktrailPublicKeys,
1470
                            keyIndex,
1471
                            result.segwit || 0,
1472
                            self.testnet,
1473
                            self.regtest,
1474
                            checksum,
1475
                            result.upgrade_key_index,
1476
                            options.useCashAddress,
1477
                            options.bypassNewAddressCheck
1478
                        );
1479
1480
                        // pass along decrypted data to avoid extra work
1481
                        return wallet.unlock({
1482
                            walletVersion: Wallet.WALLET_VERSION_V3,
1483
                            passphrase: options.passphrase,
1484
                            primarySeed: options.primarySeed,
1485
                            secret: options.secret
1486
                        }).then(function() {
1487
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1488
                            return [
1489
                                wallet,
1490
                                {
1491
                                    walletVersion: wallet.walletVersion,
1492
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1493
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1494
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1495
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1496
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1497
                                    blocktrailPublicKeys: blocktrailPublicKeys
1498
                                }
1499
                            ];
1500
                        });
1501
                    }
1502
                );
1503
        })
1504
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1505
1506
    return deferred.promise;
1507
};
1508
1509
function verifyPublicBip32Key(bip32Key, network) {
1510
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1511
    if (typeof hk.keyPair.d !== "undefined") {
1512
        throw new Error('BIP32Key contained private key material - abort');
1513
    }
1514
1515
    if (bip32Key[1].slice(0, 1) !== "M") {
1516
        throw new Error("BIP32Key contained non-public path - abort");
1517
    }
1518
}
1519
1520
function verifyPublicOnly(walletData, network) {
1521
    verifyPublicBip32Key(walletData.primary_public_key, network);
1522
    verifyPublicBip32Key(walletData.backup_public_key, network);
1523
}
1524
1525
/**
1526
 * create wallet using the API
1527
 *
1528
 * @param identifier            string      the wallet identifier to create
1529
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1530
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1531
 * @param primaryMnemonic       string      mnemonic to store
1532
 * @param checksum              string      checksum to store
1533
 * @param keyIndex              int         keyIndex that was used to create wallet
1534
 * @param segwit                bool
1535
 * @returns {q.Promise}
1536
 */
1537
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1538
                                                checksum, keyIndex, segwit) {
1539
    var self = this;
1540
1541
    var postData = {
1542
        identifier: identifier,
1543
        wallet_version: Wallet.WALLET_VERSION_V1,
1544
        primary_public_key: primaryPublicKey,
1545
        backup_public_key: backupPublicKey,
1546
        primary_mnemonic: primaryMnemonic,
1547
        checksum: checksum,
1548
        key_index: keyIndex,
1549
        segwit: segwit
1550
    };
1551
1552
    verifyPublicOnly(postData, self.network);
1553
1554
    return self.client.post("/wallet", null, postData);
1555
};
1556
1557
/**
1558
 * create wallet using the API
1559
 *
1560
 * @param identifier            string      the wallet identifier to create
1561
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1562
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1563
 * @param encryptedPrimarySeed  string      openssl format
1564
 * @param encryptedSecret       string      openssl format
1565
 * @param recoverySecret        string      openssl format
1566
 * @param checksum              string      checksum to store
1567
 * @param keyIndex              int         keyIndex that was used to create wallet
1568
 * @param supportSecret         string
1569
 * @param segwit                bool
1570
 * @returns {q.Promise}
1571
 */
1572
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1573
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1574
    var self = this;
1575
1576
    var postData = {
1577
        identifier: identifier,
1578
        wallet_version: Wallet.WALLET_VERSION_V2,
1579
        primary_public_key: primaryPublicKey,
1580
        backup_public_key: backupPublicKey,
1581
        encrypted_primary_seed: encryptedPrimarySeed,
1582
        encrypted_secret: encryptedSecret,
1583
        recovery_secret: recoverySecret,
1584
        checksum: checksum,
1585
        key_index: keyIndex,
1586
        support_secret: supportSecret || null,
1587
        segwit: segwit
1588
    };
1589
1590
    verifyPublicOnly(postData, self.network);
1591
1592
    return self.client.post("/wallet", null, postData);
1593
};
1594
1595
/**
1596
 * create wallet using the API
1597
 *
1598
 * @param identifier            string      the wallet identifier to create
1599
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1600
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1601
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1602
 * @param encryptedSecret       Buffer      buffer of ciphertext
1603
 * @param recoverySecret        Buffer      buffer of recovery secret
1604
 * @param checksum              string      checksum to store
1605
 * @param keyIndex              int         keyIndex that was used to create wallet
1606
 * @param supportSecret         string
1607
 * @param segwit                bool
1608
 * @returns {q.Promise}
1609
 */
1610
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1611
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1612
    var self = this;
1613
1614
    var postData = {
1615
        identifier: identifier,
1616
        wallet_version: Wallet.WALLET_VERSION_V3,
1617
        primary_public_key: primaryPublicKey,
1618
        backup_public_key: backupPublicKey,
1619
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1620
        encrypted_secret: encryptedSecret.toString('base64'),
1621
        recovery_secret: recoverySecret.toString('hex'),
1622
        checksum: checksum,
1623
        key_index: keyIndex,
1624
        support_secret: supportSecret || null,
1625
        segwit: segwit
1626
    };
1627
1628
    verifyPublicOnly(postData, self.network);
1629
1630
    return self.client.post("/wallet", null, postData);
1631
};
1632
1633
/**
1634
 * create wallet using the API
1635
 *
1636
 * @param identifier            string      the wallet identifier to create
1637
 * @param postData              object
1638
 * @param [cb]                  function    callback(err, result)
1639
 * @returns {q.Promise}
1640
 */
1641
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1642
    var self = this;
1643
1644
    return self.client.post("/wallet/" + identifier, null, postData, cb);
1645
};
1646
1647
/**
1648
 * upgrade wallet to use a new account number
1649
 *  the account number specifies which blocktrail cosigning key is used
1650
 *
1651
 * @param identifier            string      the wallet identifier
1652
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1653
 * @param keyIndex              int         keyIndex that was used to create wallet
1654
 * @param [cb]                  function    callback(err, result)
1655
 * @returns {q.Promise}
1656
 */
1657
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1658
    var self = this;
1659
1660
    return self.client.post("/wallet/" + identifier + "/upgrade", null, {
1661
        key_index: keyIndex,
1662
        primary_public_key: primaryPublicKey
1663
    }, cb);
1664
};
1665
1666
/**
1667
 * get the balance for the wallet
1668
 *
1669
 * @param identifier            string      the wallet identifier
1670
 * @param [cb]                  function    callback(err, result)
1671
 * @returns {q.Promise}
1672
 */
1673
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1674
    var self = this;
1675
1676
    return self.client.get("/wallet/" + identifier + "/balance", null, true, cb);
1677
};
1678
1679
/**
1680
 * do HD wallet discovery for the wallet
1681
 *
1682
 * @param identifier            string      the wallet identifier
1683
 * @param [cb]                  function    callback(err, result)
1684
 * @returns {q.Promise}
1685
 */
1686
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1687
    var self = this;
1688
1689
    return self.client.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1690
};
1691
1692
1693
/**
1694
 * get a new derivation number for specified parent path
1695
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1696
 *
1697
 * @param identifier            string      the wallet identifier
1698
 * @param path                  string      the parent path for which to get a new derivation,
1699
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1700
 * @param [cb]                  function    callback(err, result)
1701
 * @returns {q.Promise}
1702
 */
1703
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1704
    var self = this;
1705
1706
    return self.client.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1707
};
1708
1709
1710
/**
1711
 * delete the wallet
1712
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1713
 *  is required to be able to delete a wallet
1714
 *
1715
 * @param identifier            string      the wallet identifier
1716
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1717
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1718
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1719
 * @param [cb]                  function    callback(err, result)
1720
 * @returns {q.Promise}
1721
 */
1722
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1723
    var self = this;
1724
1725
    if (typeof force === "function") {
1726
        cb = force;
1727
        force = false;
1728
    }
1729
1730
    return self.client.delete("/wallet/" + identifier, {force: force}, {
1731
        checksum: checksumAddress,
1732
        signature: checksumSignature
1733
    }, cb);
1734
};
1735
1736
/**
1737
 * use the API to get the best inputs to use based on the outputs
1738
 *
1739
 * the return array has the following format:
1740
 * [
1741
 *  "utxos" => [
1742
 *      [
1743
 *          "hash" => "<txHash>",
1744
 *          "idx" => "<index of the output of that <txHash>",
1745
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1746
 *          "value" => 32746327,
1747
 *          "address" => "1address",
1748
 *          "path" => "m/44'/1'/0'/0/13",
1749
 *          "redeem_script" => "<redeemScript-hex>",
1750
 *      ],
1751
 *  ],
1752
 *  "fee"   => 10000,
1753
 *  "change"=> 1010109201,
1754
 * ]
1755
 *
1756
 * @param identifier        string      the wallet identifier
1757
 * @param pay               array       {'address': (int)value}     coins to send
1758
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1759
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1760
 * @param feeStrategy       string      defaults to
1761
 * @param options
1762
 * @param [cb]              function    callback(err, utxos, fee, change)
1763
 * @returns {q.Promise}
1764
 */
1765
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1766
    var self = this;
1767
1768
    if (typeof feeStrategy === "function") {
1769
        cb = feeStrategy;
1770
        feeStrategy = null;
1771
        options = {};
1772
    } else if (typeof options === "function") {
1773
        cb = options;
1774
        options = {};
1775
    }
1776
1777
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1778
    options = options || {};
1779
1780
    var deferred = q.defer();
1781
    deferred.promise.spreadNodeify(cb);
1782
1783
    var params = {
1784
        lock: lockUTXO,
1785
        zeroconf: allowZeroConf ? 1 : 0,
1786
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1787
        fee_strategy: feeStrategy
1788
    };
1789
1790
    if (options.forcefee) {
1791
        params['forcefee'] = options.forcefee;
1792
    }
1793
1794
    deferred.resolve(
1795
        self.client.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1796
            function(result) {
1797
                return [result.utxos, result.fee, result.change, result];
1798
            },
1799
            function(err) {
1800
                if (err.message.match(/too low to pay the fee/)) {
1801
                    throw blocktrail.WalletFeeError(err);
1802
                }
1803
1804
                throw err;
1805
            }
1806
        )
1807
    );
1808
1809
    return deferred.promise;
1810
};
1811
1812
/**
1813
 * @param [cb]              function    callback(err, utxos, fee, change)
1814
 * @returns {q.Promise}
1815
 */
1816
APIClient.prototype.feePerKB = function(cb) {
1817
    var self = this;
1818
1819
    var deferred = q.defer();
1820
    deferred.promise.spreadNodeify(cb);
1821
1822
    deferred.resolve(self.client.get("/fee-per-kb"));
1823
1824
    return deferred.promise;
1825
};
1826
1827
/**
1828
 * send the transaction using the API
1829
 *
1830
 * @param identifier        string      the wallet identifier
1831
 * @param txHex             string      partially signed transaction as hex string
1832
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1833
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1834
 * @param [twoFactorToken]  string      2FA token
1835
 * @param [prioboost]       bool
1836
 * @param [cb]              function    callback(err, txHash)
1837
 * @returns {q.Promise}
1838
 */
1839
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1840
    var self = this;
1841
1842
    if (typeof twoFactorToken === "function") {
1843
        cb = twoFactorToken;
1844
        twoFactorToken = null;
1845
        prioboost = false;
1846
    } else if (typeof prioboost === "function") {
1847
        cb = prioboost;
1848
        prioboost = false;
1849
    }
1850
1851
    var data = {
1852
        paths: paths,
1853
        two_factor_token: twoFactorToken
1854
    };
1855
    if (typeof txHex === "string") {
1856
        data.raw_transaction = txHex;
1857
    } else if (typeof txHex === "object") {
1858
        Object.keys(txHex).map(function(key) {
1859
            data[key] = txHex[key];
1860
        });
1861
    }
1862
1863
    return self.client.post(
1864
        "/wallet/" + identifier + "/send",
1865
        {
1866
            check_fee: checkFee ? 1 : 0,
1867
            prioboost: prioboost ? 1 : 0
1868
        },
1869
        data,
1870
        cb
1871
    );
1872
};
1873
1874
/**
1875
 * setup a webhook for this wallet
1876
 *
1877
 * @param identifier        string      the wallet identifier
1878
 * @param webhookIdentifier string      identifier for the webhook
1879
 * @param url               string      URL to receive webhook events
1880
 * @param [cb]              function    callback(err, webhook)
1881
 * @returns {q.Promise}
1882
 */
1883
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
1884
    var self = this;
1885
1886
    return self.client.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
1887
};
1888
1889
/**
1890
 * delete a webhook that was created for this wallet
1891
 *
1892
 * @param identifier        string      the wallet identifier
1893
 * @param webhookIdentifier string      identifier for the webhook
1894
 * @param [cb]              function    callback(err, success)
1895
 * @returns {q.Promise}
1896
 */
1897
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
1898
    var self = this;
1899
1900
    return self.client.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
1901
};
1902
1903
/**
1904
 * get all transactions for an wallet (paginated)
1905
 *
1906
 * @param identifier    string      wallet identifier
1907
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1908
 * @param [cb]          function    callback function to call when request is complete
1909
 * @return q.Promise
1910
 */
1911
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
1912
    var self = this;
1913
1914
    if (typeof params === "function") {
1915
        cb = params;
1916
        params = null;
1917
    }
1918
1919
    return self.client.get("/wallet/" + identifier + "/transactions", params, true, cb);
1920
};
1921
1922
/**
1923
 * get all addresses for an wallet (paginated)
1924
 *
1925
 * @param identifier    string      wallet identifier
1926
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1927
 * @param [cb]          function    callback function to call when request is complete
1928
 * @return q.Promise
1929
 */
1930
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
1931
    var self = this;
1932
1933
    if (typeof params === "function") {
1934
        cb = params;
1935
        params = null;
1936
    }
1937
1938
    return self.client.get("/wallet/" + identifier + "/addresses", params, true, cb);
1939
};
1940
1941
/**
1942
 * @param identifier    string      wallet identifier
1943
 * @param address       string      the address to label
1944
 * @param label         string      the label
1945
 * @param [cb]          function    callback(err, res)
1946
 * @return q.Promise
1947
 */
1948
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
1949
    var self = this;
1950
1951
    return self.client.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
1952
};
1953
1954
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
1955
    var self = this;
1956
1957
    if (typeof feeStrategy === "function") {
1958
        cb = feeStrategy;
1959
        feeStrategy = null;
1960
    } else if (typeof options === "function") {
1961
        cb = options;
1962
        options = {};
1963
    }
1964
1965
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1966
    options = options || {};
1967
1968
    var params = {
1969
        outputs: options.outputs ? options.outputs : 1,
1970
        zeroconf: allowZeroConf ? 1 : 0,
1971
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1972
        fee_strategy: feeStrategy
1973
    };
1974
1975
    if (options.forcefee) {
1976
        params['forcefee'] = options.forcefee;
1977
    }
1978
1979
    return self.client.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
1980
};
1981
1982
/**
1983
 * get all UTXOs for an wallet (paginated)
1984
 *
1985
 * @param identifier    string      wallet identifier
1986
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1987
 * @param [cb]          function    callback function to call when request is complete
1988
 * @return q.Promise
1989
 */
1990
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
1991
    var self = this;
1992
1993
    if (typeof params === "function") {
1994
        cb = params;
1995
        params = null;
1996
    }
1997
1998
    return self.client.get("/wallet/" + identifier + "/utxos", params, true, cb);
1999
};
2000
2001
/**
2002
 * get a paginated list of all wallets associated with the api user
2003
 *
2004
 * @param [params]      object      pagination: {page: 1, limit: 20}
2005
 * @param [cb]          function    callback function to call when request is complete
2006
 * @return q.Promise
2007
 */
2008
APIClient.prototype.allWallets = function(params, cb) {
2009
    var self = this;
2010
2011
    if (typeof params === "function") {
2012
        cb = params;
2013
        params = null;
2014
    }
2015
2016
    return self.client.get("/wallets", params, true, cb);
2017
};
2018
2019
/**
2020
 * verify a message signed bitcoin-core style
2021
 *
2022
 * @param message        string
2023
 * @param address        string
2024
 * @param signature      string
2025
 * @param [cb]          function    callback function to call when request is complete
2026
 * @return q.Promise
2027
 */
2028
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2029
    var self = this;
2030
2031
    // we could also use the API instead of the using bitcoinjs-lib to verify
2032
    // return self.client.post("/verify_message", null, {message: message, address: address, signature: signature}, cb);
2033
2034
    var deferred = q.defer();
2035
    deferred.promise.nodeify(cb);
2036
    try {
2037
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2038
        deferred.resolve(result);
2039
    } catch (e) {
2040
        deferred.reject(e);
2041
    }
2042
2043
    return deferred.promise;
2044
};
2045
2046
/**
2047
 * max is 0.001
2048
 * testnet only
2049
 *
2050
 * @param address
2051
 * @param amount
2052
 * @param cb
2053
 */
2054
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2055
    var self = this;
2056
2057
    return self.client.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2058
};
2059
2060
/**
2061
 * send a raw transaction
2062
 *
2063
 * @param rawTransaction    string      raw transaction as HEX
2064
 * @param [cb]              function    callback function to call when request is complete
2065
 * @return q.Promise
2066
 */
2067
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2068
    var self = this;
2069
2070
    return self.client.post("/send-raw-tx", null, rawTransaction, cb);
2071
};
2072
2073
/**
2074
 * get the current price index
2075
 *
2076
 * @param [cb]          function    callback({'USD': 287.30})
2077
 * @return q.Promise
2078
 */
2079
APIClient.prototype.price = function(cb) {
2080
    var self = this;
2081
2082
    return self.client.get("/price", null, false, cb);
2083
};
2084
2085
module.exports = APIClient;
2086